/****

    activequant - activestocks.eu

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

	
	contact  : contact@activestocks.eu
    homepage : http://www.activestocks.eu

 ****/
package org.activequant.broker.ib;

import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import org.activequant.broker.BrokerBase;
import org.activequant.core.domainmodel.InstrumentSpecification;
import org.activequant.core.domainmodel.account.Order;
import org.activequant.core.domainmodel.events.OrderAcceptEvent;
import org.activequant.core.domainmodel.events.OrderCancelEvent;
import org.activequant.core.domainmodel.events.OrderCommissionEvent;
import org.activequant.core.domainmodel.events.OrderErrorEvent;
import org.activequant.core.domainmodel.events.OrderEvent;
import org.activequant.core.domainmodel.events.OrderExecutionEvent;
import org.activequant.core.domainmodel.events.OrderRejectEvent;
import org.activequant.core.domainmodel.events.OrderSubmitEvent;
import org.activequant.core.domainmodel.events.OrderUpdateEvent;
import org.activequant.core.types.Currency;
import org.activequant.core.types.OrderSide;
import org.activequant.core.types.OrderType;
import org.activequant.core.types.TimeStamp;
import org.activequant.core.util.TimeStampFormat;
import org.activequant.util.ib.IBEventListener;
import org.activequant.util.ib.IBTwsConnection;
import org.activequant.util.tools.UniqueDateGenerator;

import com.ib.client.Contract;
import com.ib.client.EWrapperMsgGenerator;
import com.ib.client.OrderState;

/**
 * a broker implementation for trading through ib. <br>
 * <br>
 * IBBroker extends BrokerBase. Holds the following associated variables:
 * <ul>
 * <li>connection(IBTwsConnection)</li>
 * <li>ibEventListener(IBEventListener)</li>
 * <li>orderMap(Map&lt;Integer,OrderTracker&gt;)</li>
 * <li>timeStampGenerator(UniqueDateGenerator)</li>
 * </ul>
 * Holds the following inherited variables:
 * <ul>
 * <li>logger(IAQLogger)</li>
 * <li>originalOrderMap(Map&lt;String,OrderTrackerBase&gt;)</li>
 * </ul>
 * <b>History:</b><br> 
 * - [05.07.2007] Created (Ulrich Staudinger)<br> 
 * - [06.11.2007] adding order id mapping (Ulrich Staudinger)<br>
 * - [06.11.2007] Adding manual trailing stop facility (Ulrich Staudinger)<br>
 * - [13.11.2007] Order id cleanup (Erik Nijkamp)<br>
 * - [14.11.2007] Move to latest broker code (Mike Kroutikov) <br>
 * - [09.12.2007] Converted to light-weight broker api (Mike Kroutikov) <br>
 * 
 * @author Ulrich Staudinger
 * @author Mike Kroutikov
 */
public class IBBroker extends BrokerBase {
	/**
	 * private IBTwsConnection connection = null;
	 */
	private IBTwsConnection connection = null;
	/**
	 * private final IBEventListener ibEventListener = new IBEventListener() {...}<br/>
	 * The listener that finds the right  OrderTracker from the associated orderMap(Map&lt;Integer,OrderTracker&gt;) and propagates the information to to it
	 */
 	private final IBEventListener ibEventListener = new IBEventListener() {
 		/**
 		 * If there is a tracker(OrderTracker) mapped under the given orderId(int) in the orderMap(Map&lt;Integer,OrderTracker&gt;),
		 * it invokes <code>tracker.handleExecDetails(ibContract, ibExec)</code> using the given ibContract(Contract) and ibExec(Execution).<br/>
		 * This method will fire an OrderExecutionEvent which is then properly handled.
 		 */
		public void execDetails(int reqId, com.ib.client.Contract ibContract, com.ib.client.Execution ibExec) {
			String msg = EWrapperMsgGenerator.execDetails(reqId, ibContract, ibExec);
			if(reqId==1){//reqId=1 for the request when we connect
				log.info("[execDetails] for the reqExecutions occuring at connect:\n"+msg);
//				getConnection().setPreviouslyOpenedOrdersAlreadyLoaded(true);
				return;
			}
			
			//this was wrong (it is fixed now), orderId is actually reqId that was sent was sent with reqExecutions
			//OrderTracker tracker = orderMap.get(orderId);//this was wrong
			OrderTracker tracker = orderMap.get(ibExec.m_orderId);
			
			if(tracker != null) {
				log.info("[execDetails] at order fill:\n"+msg);
				tracker.handleExecDetails(ibContract, ibExec);
				return;
			}
			//reqId will be -1 for any order unless I specifically asked for it with my own requestId
//			if(reqId==1){//reqId=1 for the request when we connect
//				log.info("[execDetails] for the reqExecutions occuring at connect:\n"+msg);
//			}else{
			log.warn("[execDetails] orphan exec detail event:\n" + msg);
			//}
			
		}
		/**
		 * this method gets called after (possibly multiple) call to execDetails
		 */
		public void execDetailsEnd(int reqId){
//			if(reqId==1){//reqId=1 for the request when we connect
//				log.info("[execDetailsEnd] for the reqExecutions at connect");
//			}else{
//				log.info("[execDetailsEnd] for reqId="+reqId);
//			}
		}
		
		/**
		 * If there is a tracker(OrderTracker) mapped under the given orderId(int) in the orderMap(Map&lt;Integer,OrderTracker&gt;),
		 * it invokes <code>tracker.handleOrderStatus(status, filled, remaining, avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeld)</code> using the given variables.<br/>
		 * This method will either fire an OrderCancelEvent or OrderUpdateEvent. Needs more work!
		 */
		public void orderStatus(int orderId, String status, int filled, int remaining, double avgFillPrice, int permId, int parentId, double lastFillPrice, int clientId, String whyHeld) {
			OrderTracker tracker = orderMap.get(orderId);
			if(tracker != null) {
				tracker.handleOrderStatus(status, filled, remaining, avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeld);
				return;
			}else if(orderId<0){
				//order submitted through TWS
				log.info("[orderStatus]: this order was submitted through TWS so we don't care?\n");
			}else if(getConnection().arePreviouslyOpenedOrdersAlreadyLoaded()){			
				log.warn("orphaned event [orderStatus] " + orderId + " / " + filled);
			}else{
				log.info("[orderStatus] restored previously submitted order, not sent by TWS");
				originalOrderMap.put("IB-" + Integer.toString(orderId), null);
				orderMap.put(orderId, null);
			}
			
			
		}
		/**
		 * An empty method
		 * @see IBAccountManagingBroker
		 */
		public void updateAccountValue(String informationType, String amount, String currency, String account) {
			// see IBAccountManagingBroker
		}
		/**
		 * An empty method
		 * @see IBAccountManagingBroker
		 */
		public void updatePortfolio(com.ib.client.Contract instrument, int positionCount, double marketPrice, double marketValue, double avgCost, double unrealizedPNL, double realizedPNL, String accountName) {
			// see IBAccountManagingBroker
		}
		/**
		 * If there is a tracker(OrderTracker) mapped under the given orderId(int) in the orderMap(Map&lt;Integer,OrderTracker&gt;),
		 * it invokes <code>tracker.error(errorCode, message)</code> using the given errorCode(int) and message(String). This method knows whether it needs to fire
		 * an OrderCancelEvent, OrderRejectEvent ot OrderErrorEvent event.
		 */
		public void error(int orderId, int errorCode, String message) {
			OrderTracker tracker = orderMap.get(orderId);
			if(tracker != null) {
				tracker.error(errorCode, message);
				return;
			}
			log.warn("orphaned event [error] " + orderId + ", code=" + errorCode + ", " + message);
		}
		/**
		 * If there is a tracker(OrderTracker) mapped under the given orderId(int) in the orderMap(Map&lt;Integer,OrderTracker&gt;),
		 * it invokes <code>tracker.openOrder(contract, order)</code> using the given IB contract(Contract) and order(Order). (this method just logs some information).<br/>
		 * Otherwise it assumes it was a previously opened order that hasn't been filled yet and it restores it.<br/>
		 * Then it returns without doing anything else.
		 */
		public void openOrder(int orderId, Contract contract, com.ib.client.Order twsOrder, OrderState orderState) {
			String msg = EWrapperMsgGenerator.openOrder( orderId, contract, twsOrder, orderState);// this is to get the text info
			OrderTracker tracker = orderMap.get(orderId);
			if(tracker != null) {
				log.info("[openOrder]:"+msg);
				tracker.openOrder(contract, twsOrder, orderState);
				return;
			}else if(orderId<0){
				//order submitted through TWS
				log.info("[openOrder]: this order was submitted through TWS so we don't care?\n"+msg);
			}else{
				//the order got lost when we restarted the JDK
				Order order=convertToOrder(twsOrder,orderId);
				InstrumentSpecification spec = null;
				try {
					spec = IBTwsConnection.convertToInstrument(contract);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				order.setInstrumentSpecification(spec);
				OrderTracker restoredTracker = new OrderTracker(order,orderId);
				restoredTracker.openOrder(contract, twsOrder, orderState);
				if(getConnection().arePreviouslyOpenedOrdersAlreadyLoaded()){
					log.info("[openOrder] restored order:" + msg);					
				}else{
					log.info("[openOrder] restored order at start:" + msg);
					originalOrderMap.put("IB-" + Integer.toString(orderId), restoredTracker);
				}
				
			}
		}
	};

	/**
	 * private final Map&lt;Integer,OrderTracker&gt; orderMap = new ConcurrentHashMap&lt;Integer,OrderTracker&gt;();<br/>
	 * for decoding events
	 */
	private final Map<Integer,OrderTracker> orderMap = new ConcurrentHashMap<Integer,OrderTracker>();
	
//	public IOrderTracker[] getOrders2(){
//		return orderMap.values().toArray(new IOrderTracker[]{});
//	}
	/**
	 * private final Map&lt;InstrumentSpecification,OrderTracker&gt; previouslyOpenedOrders = new ConcurrentHashMap&lt;InstrumentSpecification,OrderTracker&gt;();<br/>
	 * This is for keeping track on Orders opened in a previous run (orders that were already opened when I connected to TWS)
	 */
	//private final Map<InstrumentSpecification,OrderTracker> previouslyOpenedOrders = new ConcurrentHashMap<InstrumentSpecification,OrderTracker>();
	/**
	 * constructs an IBBroker(extends BrokerBase) using the given connection(IBTwsConnection) to set its associated connection(IBTwsConnection).<br/>
	 * Connects to the TWS by invoking <code>connection.connect()</code> on that connection(IBTwsConnection).<br/>
	 * Adds the associated ibEventListener(IBEventListener) to the ibEventListeners(Queue&lt;IBEventListener&gt;) (if its not already there) of the associated connection(IBTwsConnection).
	 * @param connection
	 */
	public IBBroker(IBTwsConnection connection) {
		//brokerAccount = new BrokerAccount();
		this.connection = connection;
		connection.connect();
		connection.addIBEventListener(ibEventListener);
	}
	
	/**
	 * returns the associated connection(IBTwsConnection)
	 * @return
	 */
	public IBTwsConnection getConnection() {
		return connection;
	}
	/**
	 * sets the associated connection(IBTwsConnection) with the given connection(IBTwsConnection)
	 * @param connection
	 */
	public void setConnection(IBTwsConnection connection) {
		this.connection = connection;
	}

	/**
	 * this method converts an AQ order to a broker specific TWS order object.<br/>
	 * returns an IB Order from the given AQ o(Order) and orderId(int). (I need to check this method against IB samples)
	 * @param o
	 * @return
	 */
	private com.ib.client.Order convertToTwsOrder(Order o, int orderId){
		// need to convert the order into an IB order object.

		com.ib.client.Order twsOrder = new com.ib.client.Order();

		twsOrder.m_orderId = orderId;
		twsOrder.m_clientId = connection.getClientId();
		twsOrder.m_transmit = true;
		switch(o.getOrderSide()) {
		case BUY:
			twsOrder.m_action = "BUY";
			break;
		case SELL:
			twsOrder.m_action = "SELL";
			break;
		case SHORT_SELL:
			twsOrder.m_action = "SSHORT";
			break;
		case SHORT_SELL_EXEMPT:
			twsOrder.m_action = "SSHORT";
			break;
		default:
			throw new IllegalArgumentException("unsupported order side value: " + o.getOrderSide());
		} 

		// set the quantity. IB supports only integers.
		twsOrder.m_totalQuantity = (int) o.getQuantity();

		switch(o.getOrderType()) {
		case MARKET: 
			twsOrder.m_orderType = "MKT";
			break;
		case LIMIT: 
			twsOrder.m_orderType = "LMT";
			twsOrder.m_lmtPrice = o.getLimitPrice();
			break;
		case STOP: 
			twsOrder.m_orderType = "STP";
			twsOrder.m_auxPrice = o.getStopPrice();
			break;
		case STOP_LIMIT:
			twsOrder.m_orderType = "STP LMT";
			twsOrder.m_lmtPrice = o.getLimitPrice();
			twsOrder.m_auxPrice = o.getStopPrice(); //we probably need this but I'll double check
			break;
		case TRAILING_STOP:
			twsOrder.m_auxPrice = o.getTrailingDistance();
			if(o.getLimitPrice() > 0.0 && o.getStopPrice() > 0) {
				// TODO: this needs more research
				twsOrder.m_orderType      = "TRAIL LIMIT";
				twsOrder.m_lmtPrice       = o.getLimitPrice();
				twsOrder.m_trailStopPrice = o.getStopPrice();
			} else {
				twsOrder.m_orderType = "TRAIL";
			}
			break;
		case MARKET_TO_LIMIT: 
			twsOrder.m_orderType = "MTL";
			break;	
		default:
			throw new IllegalArgumentException("order type: " + o.getOrderType() + " is not supported by this broker");
		}
		
		return twsOrder;
	}
	
	private Order convertToOrder(com.ib.client.Order twsOrder, int orderId){
		Order order=new Order();
		order.setId((long)orderId);
		if("BUY".equals(twsOrder.m_action)){
			order.setOrderSide(OrderSide.BUY);
		}else if("SELL".equals(twsOrder.m_action)){
			order.setOrderSide(OrderSide.SELL);
		}else if("SSHORT".equals(twsOrder.m_action)){
			order.setOrderSide(OrderSide.SHORT_SELL);
		}else{
			throw new IllegalArgumentException("unsupported order side value: " + twsOrder.m_action);
		}
		
		order.setQuantity(twsOrder.m_totalQuantity);
		
		if("MKT".equals(twsOrder.m_orderType)){
			order.setOrderType(OrderType.MARKET);
		}else if("LMT".equals(twsOrder.m_orderType)){
			order.setOrderType(OrderType.LIMIT);
			order.setLimitPrice(twsOrder.m_lmtPrice);
		}else if("STP".equals(twsOrder.m_orderType)){
			order.setOrderType(OrderType.STOP);
			order.setStopPrice(twsOrder.m_auxPrice);
		}else if("STP LMT".equals(twsOrder.m_orderType)){
			order.setOrderType(OrderType.STOP_LIMIT);
			order.setLimitPrice(twsOrder.m_lmtPrice);
			order.setStopPrice(twsOrder.m_auxPrice);
		}else if("TRAIL LIMIT".equals(twsOrder.m_orderType)){
			order.setOrderType(OrderType.TRAILING_STOP);
			order.setLimitPrice(twsOrder.m_lmtPrice);
			order.setTrailingDistance(twsOrder.m_auxPrice);
			order.setStopPrice(twsOrder.m_trailStopPrice);
		}else if("MTL".equals(twsOrder.m_orderType)){
			order.setOrderType(OrderType.MARKET_TO_LIMIT);
		}else{
			throw new IllegalArgumentException("unsupported order type value: " + twsOrder.m_orderType);
		}
		return order;
	}
	
	/**
	 * private final UniqueDateGenerator timeStampGenerator = new UniqueDateGenerator();
	 */
	private final UniqueDateGenerator timeStampGenerator = new UniqueDateGenerator();
	/**
	 * returns a TimeStamp set with the current time (up to a milliseconds resolution) and then uses the associated timeStampGenerator(UniqueDateGenerator) to get a unique nanoseconds value.
	 * @return
	 */
	private synchronized TimeStamp currentTimeStamp() {
		return timeStampGenerator.generate(new Date());
	}
	/**
	 * OrderTracker extends OrderTrackerBase. Holds the following associated variables:
	 * <ul>
	 * <li>orderId(int)</li>
	 * <li>spec(InstrumentSpecification)</li>
	 * <li>sdf(TimeStampFormat)</li>
	 * <li>pendingUpdate(AtomicReference&lt;Order&gt;)</li>
	 * </ul>
	 * Holds the following inherited variables:
	 * <ul>
	 * <li>orderEvent(Event&lt;OrderEvent&gt;)</li>
	 * <li>order(Order)</li>
	 * <li>isSubmitted(AtomicBoolean)</li>
	 * <li>isCanceled(AtomicBoolean)</li>
	 * <li>brokerAssignedId(String)</li>
	 * <li>completion(AtomicReference&lt;OrderCompletionEvent&gt;)</li>
	 * <li>commission(AtomicReference&lt;OrderCommissionEvent&gt;)</li>
	 * <li>openQuantity(double)</li>
	 * <li>totalQuantity(double)</li>
	 * <li>executions(List&lt;OrderExecutionEvent&gt;)</li>
	 * <li>commissions(List&lt;OrderCommissionEvent&gt;)</li>
	 * </ul>
	 * @author Dimitar
	 *
	 */
	class OrderTracker extends OrderTrackerBase {
		/**
		 * private final int orderId;
		 */
		private final int orderId;
		/**
		 * private final InstrumentSpecification spec;
		 */
		private final InstrumentSpecification spec;
		/**
		 * private final AtomicBoolean isAccepted  = new AtomicBoolean(false);
		 */
		private final AtomicBoolean isAccepted  = new AtomicBoolean(false);
		/**
		 * Constructs an OrderTracker(extends OrderTrackerBase) using the given order(Order) and its quantity(double) to set its inherited order(Order) and openQuantity(double).<br/>
		 * Sets its orderId(int) with the next orderId(int) from the connection(IBTwsConnection).<br/>
		 * Sets its spec(InstrumentSpecification) with the instrumentSpecification(InstrumentSpecification) of the given order(Order).<br/>
		 * Maps this OrderTracker into the orderMap(Map&lt;Integer,OrderTracker&gt;) using the orderId(int) as key.
		 * @param order
		 */
		public OrderTracker(Order order) {
			super(order);
			orderId = connection.getNextOrderId();
			spec = order.getInstrumentSpecification();
			orderMap.put(orderId, this);
		}
		/**
		 * this one is to use when we need to reconstruct orders from execDetails/openOrders after restarting our program
		 * @param order
		 * @param orderId
		 */
		public OrderTracker(Order order, int orderId) {
			super(order);
			this.orderId =orderId;
			spec = order.getInstrumentSpecification();
			orderMap.put(orderId, this);
		}
		/**
		 * <strong>1.</strong> Creates an IB twsOrder(Order) by converting the inherited order(Order) and associated orderId(int).<br/>
		 * <strong>2.</strong> Instructs the TWS to place that twsOrder(Order) for the associated spec(InstrumentSpecification) by invoking
		 * <code>connection.placeOrder(spec, twsOrder)</code> on the connection(IBTwsConnection) and then returns a String in the form "IB-${orderId}"
		 */
		protected String handleSubmit() {
			try {
				com.ib.client.Order twsOrder = convertToTwsOrder(getOrder(), orderId);
				connection.placeOrder(spec, twsOrder);
				OrderEvent event = new OrderSubmitEvent(currentTimeStamp(),"ok");						
				super.fireOrderEvent(event);
				return "IB-" + Integer.toString(orderId);
			} catch(Exception ex) {
				throw new RuntimeException(ex);
			}
		}
		/**
		 * <strong>1.</strong> If there already is a an Order pending update in the associated pendingUpdate(AtomicReference&lt;Order&gt;),
		 * it throws an IllegalStateException("another update is pending: can not proceed").<br/> 
		 * <strong>2.</strong> Otherwise, it sets the value of the associated pendingUpdate(AtomicReference&lt;Order&gt;)
		 * with the given newOrder(Order), converts it to a twsOrder(Order) and instructs the TWS to place that twsOrder(Order) for the associated spec(InstrumentSpecification) 
		 * by invoking <code>connection.placeOrder(spec, twsOrder)</code> on the connection(IBTwsConnection) using the associated orderId(int)
		 */
		public void handleUpdate(Order newOrder) {
			if(!pendingUpdate.compareAndSet(null, newOrder)) {
				throw new IllegalStateException("another update is pending: can not proceed");
			}
			
			try {
				com.ib.client.Order twsOrder = convertToTwsOrder(newOrder, orderId);
				connection.placeOrder(spec, twsOrder);
			} catch(Exception ex) {
				pendingUpdate.set(null);
				throw new RuntimeException(ex);
			}
		}
		/**
		 * Instructs IB to cancel the order identified by the associated orderId(int) by invoking
		 * <code>connection.cancelOrder(orderId)</code> on the connection(IBTwsConnection)
		 */
		public void handleCancel() {
			try {
				connection.cancelOrder(orderId);
			} catch(Exception ex) {
				throw new RuntimeException(ex);
			}
		}
		/**
		 * private final TimeStampFormat sdf = new TimeStampFormat("yyyyMMdd  HH:mm:ss");<br/>
		 * A static block sets its TimeZone with the default one (of this machine)
		 */
		private final TimeStampFormat sdf = new TimeStampFormat("yyyyMMdd  HH:mm:ss");
		{
			// NB. tws sends date in the timezone of local machine!
			sdf.setTimeZone(TimeZone.getDefault());
		}
		/**
		 * <strong>1.</strong> creates an execution(OrderExecutionEvent) for the given ibContract(Contract) and ibExec(Execution) using the price and quantity of the given ibExec(Execution).<br/>
		 * <strong>2.</strong> fires that execution(OrderExecutionEvent). Note that this will take care of adding it to the OrderTicket, and generating Completion, if necessary.<br/>
		 * FIXME: should i use IB date or my date? not sure
		 * @param ibContract
		 * @param ibExec
		 */
		private void handleExecDetails(com.ib.client.Contract ibContract, com.ib.client.Execution ibExec) {
			try {
				if(!isAccepted.getAndSet(true)){
					super.fireOrderEvent(new OrderAcceptEvent(currentTimeStamp(),"order executed right away", Long.toString(orderId)));
				}
//				SimpleDateFormat ff = new SimpleDateFormat("yyyyMMdd   HH:mm:ss");
//				ff.setTimeZone(TimeZone.getDefault());
				
				// FIXME: should i use IB date or my date? not sure
				
				//log.info("handleExecDetails: exec_id=" + ibExec.m_execId + ", symbol=" + ibContract.m_symbol + ", shares=" + ibExec.m_shares + ", price=" + ibExec.m_price + ", time=" + ibExec.m_time);
				OrderExecutionEvent execution = new OrderExecutionEvent();

				// parse the execution date.
//				TimeStamp stamp = timeStampGenerator.generate(sdf.parse(ibExec.m_time).getDate());
//				log.info("compare stamps: " + stamp + ", now=" + new TimeStamp());
//				log.info("in text: " + sdf.format(stamp) + " " + sdf.format(new TimeStamp()));

				// does not use ib-supplied time!
				execution.setEventTimeStamp(currentTimeStamp());
				execution.setPrice(ibExec.m_price);
				execution.setQuantity(ibExec.m_shares);
				execution.setAveragePrice(ibExec.m_avgPrice);
				execution.setCumulatedQuantity(ibExec.m_cumQty);
				
				fireOrderEvent(execution);
			} catch (Exception x) {
				log.warn("[execDetails] ", x);
			}
		}
		
		/**
		 * Note: Needs more work!<br/>
		 * <strong>1.</strong> If the given status(String) is "Cancelled", it fires an OrderCancelEvent and returns.<br/>
		 * <strong>2.</strong> Otherwise it fires an OrderUpdateEvent for the Order currently held in the associated pendingUpdate(AtomicReference&lt;Order&gt;) and sets the value of pendingUpdate(AtomicReference&lt;Order&gt;) to null.<br/>
		 * (status(String) can be: "filled","Filled", "PreSubmitted", "PendingCancel", "Cancelled")
		 * @param status
		 * @param filled
		 * @param remaining
		 * @param avgFillPrice
		 * @param permId
		 * @param parentId
		 * @param lastFillPrice
		 * @param clientId
		 */
		private void handleOrderStatus(String status, int filled, int remaining, double avgFillPrice, int permId, int parentId, double lastFillPrice, int clientId, String whyHeld) {
			try {
				String message="[orderStatus] " + orderId + ": status=" + status + ", filled=" + filled + ", remaining=" + remaining + ", avgFillPrice=" + avgFillPrice + ", permId=" + permId + ", parentId=" + parentId + ", lastFillPrice=" + lastFillPrice + ", clientId=" + clientId+ ", whyHeld=" + whyHeld;
				log.info(message);
				if (status.equals("filled")) {
					// partial fill.
				} else if (status.equals("Filled")) {
					// complete fill.
				} else if (status.equals("Submitted")) {
					// order accepted through IB and working.
					if(!isAccepted.getAndSet(true)){
						super.fireOrderEvent(new OrderAcceptEvent(currentTimeStamp(),message, Long.toString(orderId)));
					}
				} else if (status.equals("PreSubmitted")) {
					// indicates that a simulated order type has been accepted by the IB system and that this order has yet to be elected. The order is held in the IB system until the election criteria are met. At that time the order is transmitted to the order destination as specified .
					// Example: "PreSubmitted" is for STOP orders, I found out by looking at the logs
					if(!isAccepted.getAndSet(true)){
						super.fireOrderEvent(new OrderAcceptEvent(currentTimeStamp(),message, Long.toString(orderId)));
					}
				} else if (status.equals("PendingSubmit")) {
					// indicates that you have transmitted the order, but have not yet received confirmation that it has been accepted by the order destination. 
					// NOTE: This order status is not sent by TWS and should be explicitly set by the API developer when an order is submitted.
				} else if (status.equals("Inactive")) {
					// indicates that the order has been accepted by the system (simulated orders) or an exchange (native orders) but that currently the order is inactive due to system, exchange or other issues.
					// i saw this in the logs when trying to submit an order after 4pm
				} else if (status.equals("PendingCancel")) {
					// cancel request received
					// indicates that you have sent a request to cancel the order but have not yet received cancel confirmation from the order destination. At this point, your order is not confirmed canceled. You may still receive an execution while your cancellation request is pending. 
					// NOTE: This order status is not sent by TWS and should be explicitly set by the API developer when an order is canceled.
				} else if (status.equals("Cancelled")) {
					// canceled upon user's request.
					super.fireOrderEvent(new OrderCancelEvent(currentTimeStamp(),"at your request"));
					return;
				}
				
				// if not cancel confirmation, check for the pending
				// update: if there, it means that no error has been issued
				// between update was sent and this "orderStatus" has been
				// received. This we use as indication that order update was
				// successful.
				Order updatedOrder = pendingUpdate.getAndSet(null);
				if(updatedOrder != null) {
					super.fireOrderEvent(new OrderUpdateEvent(currentTimeStamp(),"",updatedOrder));
				}
			} catch (Exception x) {
				log.debug("[orderStatus] details about an already filled order received. Could have been sent from another or and old IB connection.");
			}
		}
		/**
		 * private final AtomicReference&lt;Order&gt; pendingUpdate = new AtomicReference&lt;Order&gt;();
		 */
		private final AtomicReference<Order> pendingUpdate = new AtomicReference<Order>();
		/**
		 * <strong>1.</strong> If the given errorCode(int) is 202 (Order canceled from IB's side), it fires an OrderCancelEvent.<br/>
		 * <strong>2.</strong> If the given errorCode(int) is 201 (Order rejected from IB's side), it fires an OrderRejectEvent.<br/>
		 * <strong>3.</strong> Otherwise it gets the updatedOrder(Order) held in the associated pendingUpdate(AtomicReference&lt;Order&gt;) and sets the new value to null. If the updatedOrder(Order)
		 * is not null, it fires an OrderErrorEvent, otherwise it fires an OrderCancelEvent.
		 * @param errorCode
		 * @param message
		 */
		private void error(int errorCode, String message) {
			log.info("[error] " + orderId + " / " + message + "(" + errorCode + ")");
			if(errorCode == 202) {
				// canceled from IB side
				super.fireOrderEvent(new OrderCancelEvent(currentTimeStamp(), message + " (IB error code " + errorCode + ")"));
			} else if(errorCode == 201) {
				// rejected from IB side
				super.fireOrderEvent(new OrderRejectEvent(currentTimeStamp(), message + " (IB error code " + errorCode + ")"));
			} else {
				Order updatedOrder = pendingUpdate.getAndSet(null);
				if(updatedOrder != null) {
					// error doing update!
					// its not fatal!
					super.fireOrderEvent(new OrderErrorEvent(currentTimeStamp(), message + " (IB error code " + errorCode + ")"));
				} else {
					// FIXME: catch-all error. Assume its terminal state.
					// Maybe need to differentiate?
					super.fireOrderEvent(new OrderCancelEvent(currentTimeStamp(), message + " (IB error code " + errorCode + ")"));
				}
			}
		}
		/**
		 * If the given orderState(OrderState) has a commission info, it creates and fires an OrderCommissionEvent
		 * @param contract
		 * @param order
		 */
		private void openOrder(com.ib.client.Contract contract, com.ib.client.Order order, OrderState orderState) {
			//log.info("[openOrder] " + orderId + " / " + contract + "(" + order + ")");
			//log.info("[openOrder] " + orderId + " / Order: action=" + order.m_action + ", openClose=" + order.m_openClose + ", " + order.m_totalQuantity+ ", " + order.m_trailStopPrice);
			//this should also set the totalQuantity in the OrderTrackerBase, the original quantity maybe different with what IB returns back?			
			if(orderState.m_commission!=Double.MAX_VALUE){			
				OrderCommissionEvent event=new OrderCommissionEvent();
				event.setEventTimeStamp(currentTimeStamp());
				event.setCommission(orderState.m_commission);
				Currency currency = Currency.toClass(orderState.m_commissionCurrency);
				event.setCurrency(currency);
				super.fireOrderEvent(event);
			}
		}
	}
	/**
	 * Creates and returns an OrderTracker(extends OrderTrackerBase) using the given order(Order) and its quantity(double) to set its inherited order(Order) and openQuantity(double).<br/>
	 * Sets its orderId(int) with the next orderId(int) from the associated connection(IBTwsConnection).<br/>
	 * Sets its spec(InstrumentSpecification) with the instrumentSpecification(InstrumentSpecification) of the given order(Order).<br/>
	 * Maps this OrderTracker into the associated orderMap(Map&lt;Integer,OrderTracker&gt;) using the orderId(int) as key.
	 */
	@Override
	protected OrderTracker createOrderTracker(Order order) {
		return new OrderTracker(order);
	}
	
}
